﻿
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Markup;
using System.Reflection;
using System.Xaml;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using System.Reactive.Concurrency;

using alib;
using alib.Concurrency;
using alib.Debugging;
using alib.Enumerable;
using alib.Reflection;

using agree.schema;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	[AttributeUsage(AttributeTargets.Method, Inherited = true)]
	public class TriggersMonadExecutionAttribute : Attribute
	{
	};

	[AttributeUsage(AttributeTargets.Class, Inherited = false)]
	public class ResubscribableMonadAttribute : Attribute
	{
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[RuntimeNameProperty("Name")]
	[MarkupExtensionReturnType(typeof(IIdentity))]
	//[MarkupExtensionReturnType(typeof(ILoader))]
	public abstract class _monad : MarkupExtension, IMonad, IValidatableObject, ρρName
	{
#if VALIDATE_REQUIRED_FIELDS
		public static readonly ConstructorInfo ci;
		static readonly MethodInfo[] rga_reqd;
#endif

#if VALIDATE_REQUIRED_FIELDS
		static _loader()
		{
			var t = typeof(K);
			if ((ci = t.GetConstructor(TypeTuples<IIdentity,T>.Targs)) == null)
				throw new MarkupException("'{0}' must have a constructor with arguments (IIdentity, {1})", t.Name, typeof(T)._Name());
			var req_props = t.GetProperties((BindingFlags)0x14)
					   .Where(pi => pi.GetCustomAttributes().AnyOfType<RequiredAttribute>());

			if (req_props.Any())
			{
				rga_reqd = req_props
							.Where(pi => !pi.PropertyType.IsValueType)
							.Select(pi => pi.GetAccessors()[0])
							.ToArray();

				if (req_props.Any(mi => mi.PropertyType.IsAssignableFrom(typeof(T))))
					return;
			}
			throw new MarkupException("'{0}' must have a property of type '{1}' marked [Required]", t.Name, typeof(T)._Name());
		}
#endif

#if ENSURE_PARENT
		static FieldInfo _fi__xamlContext;
		static MethodInfo _mi_get_IsInitializedCallback;
		protected static xaml_obj_writer GetObjectWriter(IServiceProvider sp)
		{
			var ixli = sp.GetService(typeof(System.Xaml.IXamlLineInfo)) as System.Xaml.IXamlLineInfo;
			var Tix = ixli.GetType();

			var fi = _fi__xamlContext;
			if (fi == null || fi.DeclaringType != Tix)
				_fi__xamlContext = fi = Tix.GetField("_xamlContext", (BindingFlags)0x24);

			var _xamlContext = fi.GetValue(ixli);
			var Txc = _xamlContext.GetType();

			var mi = _mi_get_IsInitializedCallback;
			if (mi == null || mi.DeclaringType != Txc)
				_mi_get_IsInitializedCallback = mi = Txc.GetMethod("get_IsInitializedCallback", (BindingFlags)0x24);

			return (xaml_obj_writer)mi.Invoke(_xamlContext, null);
		}
		protected static AgreeSchema GetSchema(IServiceProvider sp)
		{
			if (sp == null)
				return null;
			var scp = sp.GetService(typeof(IXamlSchemaContextProvider)) as IXamlSchemaContextProvider;
			return scp != null ? scp.SchemaContext as AgreeSchema : null;
		}
		protected static Object GetXamlTargetObject(IServiceProvider sp)
		{
			var scp = GetProvideValueInfo(sp);
			return scp != null ? scp.TargetObject : null;
		}
		protected static IProvideValueTarget GetProvideValueInfo(IServiceProvider sp)
		{
			if (sp == null)
				return null;
			return sp.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
		}
#endif

		public _monad(IRtParent parent, IIdentity prv)
		{
			if (parent == null)
				throw new Exception();
			this.parent = parent;
			this.trace = prv ?? parent;
		}

		///////////////////////////////////////////////////////////////////////
		/// 
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		String name;
		public String Name
		{
			get { return this.name; }
			set { this.name = value; }
		}
		/// 
		///////////////////////////////////////////////////////////////////////

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public IRtParent parent;
		public IRtParent Parent { [DebuggerStepThrough] get { return parent; } }

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public IIdentity trace;
		//[DebuggerDisplay("{identity_base._dbg_trace(this),nq}")]
		public IIdentity Trace { [DebuggerStepThrough] get { return trace; } }

		public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
		{
#if VALIDATE_REQUIRED_FIELDS
			if (rga_reqd != null)
				for (int i = 0; i < rga_reqd.Length; i++)
				{
					Object v = null;
					bool not_impl = false;
					try
					{
						v = rga_reqd[i].Invoke(this, null);
					}
					catch (NotImplementedException)
					{
						not_impl = true;
					}
					if (v == null && !not_impl)
						yield return new ValidationResult("Required argument missing", new[] { rga_reqd[i].Name });
				}
#endif
			yield break;
		}

		protected int value_bits;
		public bool HasAllInputArgs
		{
			get { return c_required_args == 0 || value_bits == (1 << c_required_args) - 1; }
		}
		public bool HasArg(int i_arg) { return (value_bits & (1 << i_arg)) != 0; }

		protected int c_required_args;

		public virtual int InputItemsCount
		{
			get
			{
				if (this is IObserver<Object>)
					Debug.Print("{0} should probably override 'InputItemsCount'", this.GetType()._Name());
				return 0;
			}
		}

		public TaskController TaskControl { get { return parent.TaskControl; } }

		public virtual INameScope NameScope { get { return parent.NameScope; } }

		Object IServiceProvider.GetService(System.Type Tsvc)
		{
			return this.GetType().IsAssignableFrom(Tsvc) ? this : null;
		}

		public override Object ProvideValue(IServiceProvider sp)
		{
#if ENSURE_PARENT
			if (parent == null)
			{
				if (sp == null)
					throw new Exception();

				if ((parent = sp as IRuntimeParent) == null)
				{
					var xow = GetObjectWriter(sp);

					if ((parent = xow.StackInstances.FirstOfType<IRuntimeParent>()) == null)	// crucial?
						throw new Exception();
				}
			}
#endif

			if (!HasAllInputArgs)
				throw new Exception();

			return this;
		}

		public virtual void Wait() { }
		public virtual bool IsCompleted { get { return true; } }

		public override string ToString()
		{
			return String.Format("Ⓜ{0}", this.GetType()._Name());	//✱
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class _monad<T0> : _monad, ρρInputItem
	{
		public _monad(IRtParent parent, T0 t0)
			: base(parent, t0 as IIdentity)
		{
			this.c_required_args = 1;
			this.t0 = t0;
		}
		public _monad(IRtParent parent)
			: base(parent, default(IIdentity))
		{
			this.c_required_args = 1;
		}

		public Object InputItem { get { return _t0; } }

		public override int InputItemsCount
		{
			get
			{
				int c = _t0.CountIfAvail();
				return c == -1 ? 0 : c;
			}
		}

		T0 _t0;
		protected T0 t0
		{
			[DebuggerStepThrough]
			get { return _t0; }
			set
			{
				if (Object.Equals(value, _t0))
					return;
				if ((value_bits & 1) != 0)
					throw new Exception("Argument already set");
				_t0 = value;
				value_bits |= 1;
				ValidateArg0(ref _t0);
			}
		}

		protected virtual void ValidateArg0(ref T0 t1) { }

		public T0 __arg0 { get { return _t0; } }

		public override object ProvideValue(IServiceProvider sp)
		{
#if DEBUG
			base.ProvideValue(sp);

			var arg = _t0 as IIdentity;
			if (arg != null && trace != arg && sp is IFunctor)
			{
				Debug.Print("=============\r\nldr:       {0}\r\nldr-trace: {1}\r\narg:       {2}\r\narg-trace: {3}",
					this.GetType()._Name(),
					this.Trace.GetType()._Name(),
					arg.GetType()._Name(),
					arg.Trace == null ? "null" : arg.Trace.GetType()._Name());

				//this._set_trace(arg);
			}
#endif
			return this;
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class _monad<T0, T1> : _monad<T0>
	{
		public _monad(IRtParent parent, T0 t0, T1 t1)
			: base(parent, t0)
		{
			this.c_required_args = 2;
			this.t1 = t1;
		}
		public _monad(IRtParent parent, T0 t0)
			: base(parent, t0)
		{
			this.c_required_args = 2;
		}
		public _monad(IRtParent parent)
			: base(parent)
		{
			this.c_required_args = 2;
		}

		T1 _t1;
		protected T1 t1
		{
			[DebuggerStepThrough]
			get { return _t1; }
			set
			{
				if ((value_bits & 2) != 0)
					throw new Exception("Argument already set");
				if (Object.Equals(value, default(T1)))
					throw new Exception();
				_t1 = value;
				value_bits |= 2;
			}
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class _monad<T0, T1, T2> : _monad<T0, T1>
	{
		public _monad(IRtParent parent, T0 t0, T1 t1, T2 t2)
			: base(parent, t0, t1)
		{
			this.c_required_args = 3;
			this.t2 = t2;
		}
		public _monad(IRtParent parent)
			: base(parent)
		{
			this.c_required_args = 3;
		}

		T2 _t2;
		protected T2 t2
		{
			get { return _t2; }
			set
			{
				if ((value_bits & 4) != 0)
					throw new Exception("Argument already set");
				if (Object.Equals(value, default(T2)))
					throw new Exception();
				_t2 = value;
				value_bits |= 4;
			}
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class transform_monad<T, U> : _monad<T>, IMonad<T, U>
		where U : class, IIdentity
	{
		static Type[] _targs;
		static transform_monad() { _targs = TypeTuples<T, U>.Targs; }

		public transform_monad(IRtParent parent, T t)
			: base(parent, t)
		{
		}
		protected transform_monad(IRtParent parent)
			: base(parent)
		{
		}

		public Type[] Targs { get { return _targs; } }
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class m_scalar_pulse<T, U> : transform_monad<T, U>
		where U : class, IIdentity
	{
		public m_scalar_pulse(IRtParent parent, T t)
			: base(parent, t)
		{
		}

		public sealed override Object ProvideValue(IServiceProvider sp)
		{
			base.ProvideValue(sp);

			return this.Result;
		}

		protected abstract void start(T t);

		IIdentity _result;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public U Result
		{
			get
			{
				IIdentity _tmp;
				if ((_tmp = _result) == null && new identity_busy(this).InterlockedTryClaim(ref _result, out _tmp))
					start(t0);

				if (_tmp is identity_busy)
					_tmp = _tmp.InterlockedWaitPulse(ref _result);

				return (U)_tmp;
			}
		}

		protected void SetResult(U u)
		{
			u.InterlockedPublishTo(ref _result);
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 20130319 gcs: finally realized that a scalar monad is definitely not a case of observable. If the pipeline 
	/// composer has no way of knowing that there will be only a single item produced, then it must assume that 
	/// fan-out is in play, and it will raise the type of the pipeline continuing onwards.
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class m_scalar_base<T, U> : transform_monad<T, U>
		where U : class, IIdentity
	{
		public m_scalar_base(IRtParent parent, T t)
			: base(parent, t)
		{
		}
		public m_scalar_base(IRtParent parent)
			: base(parent)
		{
		}

		public sealed override Object ProvideValue(IServiceProvider sp)
		{
			U _tmp;
			if ((_tmp = u) == null)
				lock (this)
					if ((_tmp = u) == null)
					{
						base.ProvideValue(sp);
						this.u = _tmp = start(t0);
					}
			return _tmp;
		}

		protected abstract U start(T t);

		U u;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public U Result
		{
			get { return (U)ProvideValue(null); }
		}
	};

#if false
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class m_scalar_task<T, U> : m_scalar_base<T, IAsyncTask<U>>, IAsyncTask<U>
		where U : class, IIdentity
	{
		public m_scalar_task(IRtParent parent, T t)
			: base(parent, t)
		{
			this.at = new AsyncTask<U>(this, t as IIdentity);
		}

		AsyncTask<U> at;
		protected sealed override IAsyncTask<U> start(T t)
		{
			start_task(t);
			return at;
		}

		protected abstract void start_task(T t);

		public bool TrySetResult(Object value)
		{
			var u = value as U;
			return u is U && at.TrySetResult(u);
		}

		U IAsyncTask<U>.Result { get { return at.Result; } }
		Object IAsyncResult.AsyncState { get { return at.AsyncState; } }
		WaitHandle IAsyncResult.AsyncWaitHandle { get { return at.AsyncWaitHandle; } }
		bool IAsyncResult.CompletedSynchronously { get { return at.CompletedSynchronously; } }
		public override bool IsCompleted { get { return at.IsCompleted; } }
		public override void Wait() { at.Wait(); }
	};
#endif

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class m_scalar_await<T, U> : transform_monad<T, U>
		where U : class, IIdentity
	{
		public m_scalar_await(IRtParent parent, T t)
			: base(parent, t)
		{
		}

		public sealed override Object ProvideValue(IServiceProvider sp)
		{
			base.ProvideValue(sp);

			return root_task().Result;
		}

		Task<U> task;
		/// <summary>
		/// This task has nothing to do with the await operation of the monad itself (if any). It only provides
		/// a (possibly compiler-generated) awaitable which pertains to the the completion of the start function,
		/// (namely, it's returning of 'U') and not U's contentful work. In other words, 'U' may be an awaitable
		/// object. The confusing part is that, e.g. the chart controller exposes U==typeof(this), which means
		/// that you can need to await the result of the start function (here) to get 'U'--which is actually 
		/// yourself--and then await that returned object to get its result--(which actually is also itself).
		/// </summary>
		Task<U> root_task()
		{
			if (task == null)
				task = start_async(t0);
			return task;
		}

		protected abstract Task<U> start_async(T t);
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// Θ_ό
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	abstract class obs_status
	{
		public sealed class completed : obs_status
		{
			public static readonly completed Instance;
			static completed() { Instance = new completed(); }
			completed() { }
		};
		public abstract class active : obs_status
		{
			Object o_wait;

			protected void ReleaseWaiters()
			{
				Object o;
				if ((o = Interlocked.Exchange(ref o_wait, null)) != null)
				{
					Monitor.Enter(o);
					Monitor.PulseAll(o);
					Monitor.Exit(o);
				}
			}

			public void Wait()
			{
				var _tmp = o_wait;
				if (_tmp == null)
					_tmp = Interlocked.CompareExchange(ref o_wait, _tmp = new Object(), null) ?? _tmp;

				Monitor.Enter(_tmp);
				if (o_wait != null)
					Monitor.Wait(_tmp);
				Monitor.Exit(_tmp);
			}
		};
		public sealed class active<U> : active
			where U : class, IIdentity
		{
			public active(IObserver<U> obs) { this.obs = obs; }
			readonly IObserver<U> obs;
			public void OnNext(U u) { obs.OnNext(u); }
			public void OnCompleted()
			{
				obs.OnCompleted();
				ReleaseWaiters();
			}
			public void OnError(Exception ex)
			{
				obs.OnError(ex);
				ReleaseWaiters();
			}
		};
		public sealed class error : obs_status
		{
			public error(Exception ex) { this.ex = ex; }
			public readonly Exception ex;
		};
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class obs_monad<T, U> : transform_monad<T, IActiveObj<U>>, IActiveObj<U>,
		ρρOutputItemsCount
		where U : class, IIdentity
	{
		public obs_monad(IRtParent parent, T t)
			: base(parent, t)
		{
		}
		protected obs_monad(IRtParent parent)
			: base(parent)
		{
		}

		public abstract IDisposable Subscribe(IObserver<U> observer);

		public abstract int OutputItemsCount { get; }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class m_singleton<T, U> : obs_monad<T, U>
		where U : class, IIdentity
	{
		public m_singleton(IRtParent parent, T t)
			: base(parent, t)
		{
			this.rga = alib.Collections.Collection<Action>.None;
			this.mre_preroot = new ManualResetEvent(false);
			this.mre_complete = new ManualResetEvent(false);
		}

		Action[] rga;
		ManualResetEvent mre_preroot;
		ManualResetEvent mre_complete;

		void AddCompletionAction(Action a)
		{
			if (alib.Array.arr.AppendSafeIfNotNull(ref rga, a) == null)
				a();
		}

		protected void _set_complete()
		{
			if (this.root == null)
				throw new Exception("root item was not set");

			mre_preroot.Set();
			mre_complete.Set();

			var _tmp = Interlocked.Exchange(ref rga, null);
			if (_tmp != null && _tmp.Length > 0)
			{
				foreach (var a in _tmp)
					a();
			}
		}

		public override IDisposable Subscribe(IObserver<U> observer)
		{
			ensure_start();

			observer.OnNext(this.root);

			AddCompletionAction(() => observer.OnCompleted());

			return EmptyDisposable.Instance;
		}

		U root;
		protected void _set_root(U _root)
		{
			if (this.root != null)
				throw new Exception();

			this.root = _root;

			mre_preroot.Set();
		}

		public sealed override Object ProvideValue(IServiceProvider sp)
		{
			base.ProvideValue(sp);

			ensure_start();

			if (root.GetType().Name == "StringCorpus")	// todo: fix fix (use [UsableDuringInitialization] ?)
			{
				mre_complete.WaitOne();
				return root;
			}

			return this;
		}

		public override void Wait()
		{
			ensure_start();
			mre_complete.WaitOne();
		}

		public override bool IsCompleted { get { return rga == null; } }

		public override int OutputItemsCount { get { return IsCompleted ? 1 : 0; } }

		void ensure_start()
		{
			if (root == null)
			{
				lock (this)
					if (this.root == null)
						start(t0);
				mre_preroot.WaitOne();
			}
		}

		protected abstract void start(T input);
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class m_observable<T, U> : obs_monad<T, U>, IDisposable
		where U : class, IIdentity
	{
		public m_observable(IRtParent parent, T t)
			: base(parent, t)
		{
			this.c_tasks = 1;
			this.c_tasks_max = 1;
		}

		obs_status st;
		int c_tasks;
		public int c_tasks_max;
		public int c_item_count;
		U last_item;

		public override int OutputItemsCount { get { return c_item_count; } }

		public sealed override Object ProvideValue(IServiceProvider sp)
		{
			base.ProvideValue(sp);
			return this;
		}

		protected abstract void start(T input);

		public override IDisposable Subscribe(IObserver<U> obs)
		{
			if (obs == null)
				throw new ArgumentNullException("observer");

			obs_status _tmp;
			if ((_tmp = st) == null)
			{
				if (Interlocked.CompareExchange(ref st, new obs_status.active<U>(obs), null) != null)
					throw new Exception();

				SysObj.BeginApplicationTask();
				start(t0);
				return this;
			}
			else if (_tmp is obs_status.completed)
			{
				try_courtesy_replay(obs);
				return EmptyDisposable.Instance;	// returning 'this' is reserved for when EndApplicationTask is needed
			}

			throw _tmp is obs_status.error ? ((obs_status.error)_tmp).ex : new Exception();
		}

		protected void _add_task()
		{
			var c = Interlocked.Increment(ref c_tasks);
			_concurrency_ext.InterlockedMax(ref c_tasks_max, c);
		}

		protected void _remove_task()
		{
			int c;
			if ((c = Interlocked.Decrement(ref c_tasks)) > 0)
				return;

			if (c < 0)
				throw new Exception();

			var _tmp = (obs_status.active<U>)Interlocked.Exchange(ref st, obs_status.completed.Instance);
			_tmp.OnCompleted();
			SysObj.EndApplicationTask();
		}

		protected void _item(U item)
		{
			var _tmp = (obs_status.active<U>)st;
			last_item = item;
			Interlocked.Increment(ref c_item_count);
			_tmp.OnNext(last_item);
		}

		protected void _error(Exception ex)
		{
			var _tmp = st as obs_status.active<U>;
			if (_tmp != null && _tmp == Interlocked.CompareExchange(ref st, new obs_status.error(ex), _tmp))
			{
				_tmp.OnError(ex);
				SysObj.EndApplicationTask();
			}
		}

		public void Dispose()
		{
			_error(new TaskCanceledException());
		}

		public override bool IsCompleted { get { return st is obs_status.completed; } }

		public override void Wait()
		{
			obs_status _tmp;
			obs_status.error _est;

			if ((_tmp = st) is obs_status.completed)
				return;

			if (_tmp == null)
				throw new Exception("Not started");

			if ((_est = _tmp as obs_status.error) == null)
			{
				((obs_status.active<U>)_tmp).Wait();

				if ((_est = st as obs_status.error) == null)
					return;
			}
			throw _est.ex;
		}

		void try_courtesy_replay(IObserver<U> obs)
		{
			if (c_item_count == 1)
			{
				Debug.Print("courtesy replay of singleton cold observable");
				obs.OnNext(last_item);
				obs.OnCompleted();
			}
			else
			{
				var msg = String.Format(
@"Cannot subscribe to '{0}' because it is a cold observable which has already been consumed.

Courtesy replay is available for monadic singletons, but this monad generated {1} items.",
					this.GetType()._Name(),
					c_item_count);
				obs.OnError(new Exception(msg));
			}
		}
	};
}
